﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using CS = System.Configuration.ConfigurationManager;
using System.Configuration;
using System.Collections;
namespace FileMonLite
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }


        #region Variables

        Dictionary<string, int> toBackup = new Dictionary<string, int>();
        Queue<string> backupQueue = new Queue<string>();
        private string backupDirectory;
        private int backupDelay = 1000;
        private const string SET_FILE = "FILEPATH";
        private const string SET_BACK = "BACKUP_LOCATION";
        private const string SET_BACKUP_DELAY = "BACKUP_DELAY";
        List<Icon> trayIcons = new List<Icon>();
        bool? changeIcon = false;
        int iconId = 1;
        #endregion


        #region Events

        /// <summary>
        /// Initialize values from settings, attach text change events.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Load(object sender, EventArgs e)
        {
            string title = Text;
            title = string.Format("{0} {1}", title, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString());
            Text = title;
            textBox1.Text = CS.AppSettings[SET_FILE];
            textBox2.Text = CS.AppSettings[SET_BACK];
            textBox3.Text = CS.AppSettings[SET_BACKUP_DELAY];
            if (!Int32.TryParse(textBox3.Text, out backupDelay))
            {
                backupDelay = 1000;
            }
            this.textBox1.Leave += new System.EventHandler(this.textBox1_TextChanged);
            this.textBox2.Leave += new System.EventHandler(this.textBox2_TextChanged);
            this.textBox3.Leave += new System.EventHandler(this.textBox3_TextChanged);

            trayIcons.Add(Resources.db1);
            trayIcons.Add(Resources.db2);
            trayIcons.Add(Resources.db3);
            trayIcons.Add(Resources.db4);

            timer1.Enabled = true;
            InitializeWatcher();
            StartDefered(() => this.Invoke(new Action(Close)), 1000);

        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Show();
            this.BringToFront();
        }

        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.ExitThread();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true;
            this.Hide();
        }

        private void toolStripMenuItem2_Click(object sender, EventArgs e)
        {
            AboutForm.Instance.Show();

        }

        private void notifyIcon1_DoubleClick(object sender, EventArgs e)
        {
            this.Show();
            this.BringToFront();
        }

        private void button4_Click(object sender, EventArgs e)
        {
            Stop();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            Start();
            StartDefered(() => this.Invoke(new Action(Close)), 1000);
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            InitializeWatcher();
        }

        private void textBox2_TextChanged(object sender, EventArgs e)
        {

            InitializeWatcher();
        }

        private void textBox3_TextChanged(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// filesystemWatcher1 detected a change in one of the files.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
        {
            toBackup.AddOrReplace(e.FullPath, 0);
            StartDefered(() => { EnqueueForBackup(); }, backupDelay);
        }

        /// <summary>
        /// Choose backup folder.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            folderBrowserDialog1.SelectedPath = textBox2.Text;
            if (DialogResult.OK == folderBrowserDialog1.ShowDialog())
            {
                textBox2.Text = folderBrowserDialog1.SelectedPath;
                textBox2_TextChanged(null, null);
            }
        }

        /// <summary>
        /// Choose watched file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            openFileDialog1.FileName = textBox1.Text;
            if (DialogResult.OK == openFileDialog1.ShowDialog())
            {
                textBox1.Text = openFileDialog1.FileName;
                textBox1_TextChanged(null, null);
            }
        }

        /// <summary>
        /// backgroundWorkder1 queue processing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                if (backgroundWorker1.CancellationPending)
                {
                    return;
                }
                //Console.WriteLine("ping");
                if (backupQueue.Count > 0)
                {
                    Console.WriteLine("Backup...");
                    Invoke(new Action(() => { changeIcon = true; }));

                    string fp = backupQueue.Dequeue();

                    if (File.Exists(fp))
                    {
                        Console.WriteLine("BACKING UP [{0}]", fp);
                        FileInfo fi = new FileInfo(fp);

                        string backDir = Path.Combine(backupDirectory, fi.Name);
                        if (!Directory.Exists(backDir))
                        {
                            Directory.CreateDirectory(backDir);
                        }
                        SortedSet<string> ss = new SortedSet<string>();

                        foreach (var d in Directory.EnumerateDirectories(backDir, "VER*"))
                        {
                            ss.Add(Path.GetFileName(d));
                        }
                        int last = 0;
                        if (ss.Count > 0)
                        {

                            last = Int32.Parse(ss.Last().Substring(3));
                        }
                        Console.WriteLine(last);


                        string nfn = string.Format("VER{0:d6}", last + 1);
                        Console.WriteLine(nfn);
                        string destDir = Path.Combine(backDir, nfn);
                        Directory.CreateDirectory(destDir);
                        File.Copy(fp, Path.Combine(destDir, fi.Name));
                    }
                    if (backupQueue.Count == 0)
                    {
                        StartDefered(new Action(() => { changeIcon = false; }), 1000);
                    }
                }
                Thread.Sleep(50);
            }
        }

        /// <summary>
        /// Tray icon manager
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (changeIcon.HasValue)
            {
                if (changeIcon.Value)
                {
                    iconId = (iconId + 1) % trayIcons.Count - 1;
                    notifyIcon1.Icon = trayIcons[iconId + 1];
                }
                else
                {
                    notifyIcon1.Icon = trayIcons[0];
                    changeIcon = null;
                }
            }
        }
        #endregion


        #region Methods

        /// <summary>
        /// Start and action or labda expression deffered by timeOut milliseconds.
        /// </summary>
        /// <param name="action">Action or lambda</param>
        /// <param name="timeOut">in milliseconds</param>
        private void StartDefered(Action action, int timeOut = 5000)
        {
            new MethodInvoker(new Action(
                   () =>
                   {
                       System.Threading.SpinWait.SpinUntil(() => true == false, timeOut);
                       Console.WriteLine("EXECUTING {0}", action.Method.Name);
                       action();
                   })
                   ).BeginInvoke(null, null);
        }

        /// <summary>
        /// Start watching the selected files; start backgroundWorker1/
        /// </summary>
        private void Start()
        {
            pictureBox1.Enabled = true;
            InitializeWatcher();
        }

        /// <summary>
        /// Stop watching the filesystem; stop backgroundWorker1.
        /// </summary>
        private void Stop()
        {
            pictureBox1.Enabled = false;
            fileSystemWatcher1.EnableRaisingEvents = false;
            backgroundWorker1.CancelAsync();
        }

        /// <summary>
        /// Enqueue file paths for backup by backGeoundWorker1.
        /// </summary>
        private void EnqueueForBackup()
        {
            lock (toBackup)
            {
                foreach (var item in toBackup)
                {
                    Console.WriteLine("CHECK [{0}]", item.Key);
                    backupQueue.Enqueue(item.Key);

                }
                toBackup.Clear();
            }
        }

        /// <summary>
        /// Initialize fileSystemWatcher1.
        /// </summary>
        private void InitializeWatcher()
        {
            string fn = textBox1.Text;
            if (string.IsNullOrWhiteSpace(fn))
            {
                return;
            }
            try
            {

                Path.GetDirectoryName(textBox1.Text);
            }
            catch
            {
                return;
            }

            fileSystemWatcher1.EnableRaisingEvents = false;
            pictureBox1.Enabled = false;

            fileSystemWatcher1.Filter = Path.GetFileName(textBox1.Text);
            fileSystemWatcher1.Path = Path.GetDirectoryName(textBox1.Text);

            System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

            config.AppSettings.Settings[SET_FILE].Value = textBox1.Text;
            config.AppSettings.Settings[SET_BACK].Value = textBox2.Text;
            config.AppSettings.Settings[SET_BACKUP_DELAY].Value = textBox3.Text;

            if (!Int32.TryParse(textBox3.Text, out backupDelay))
            {
                backupDelay = 1000;
            }

            config.Save(ConfigurationSaveMode.Modified);

            backupDirectory = config.AppSettings.Settings[SET_BACK].Value;

            if (!Directory.Exists(backupDirectory))
            {
                if (!string.IsNullOrWhiteSpace(backupDirectory))
                {
                    Directory.CreateDirectory(backupDirectory);
                }
            }

            fileSystemWatcher1.EnableRaisingEvents = true;
            if (!backgroundWorker1.IsBusy)
                backgroundWorker1.RunWorkerAsync();

            pictureBox1.Enabled = true;
        }

        #endregion


    }


    /// <summary>
    /// Extension helper method for dictionary
    /// </summary>
    public static class Extensions
    {
        public static void AddOrReplace<X, Z>(this Dictionary<X, Z> ths, X key, Z value)
        {
            if (ths.ContainsKey(key))
            {
                ths[key] = value;
            }
            else
            {
                ths.Add(key, value);
            }
        }
    }

}
